#!/usr/bin/env node

'use strict'

require('colors')
const assert = require('assert')
const async = require('async')
const childProcess = require('child_process')
const fs = require('fs-extra')
const glob = require('glob')
const path = require('path')

const CONFIG = require('./config')
const backupNodeModules = require('./lib/backup-node-modules')
const runApmInstall = require('./lib/run-apm-install')

const resourcePath = CONFIG.repositoryRootPath
let executablePath
if (process.platform === 'darwin') {
  const executablePaths = glob.sync(path.join(CONFIG.buildOutputPath, '*.app'))
  assert(executablePaths.length === 1, `More than one application to run tests against was found. ${executablePaths.join(',')}`)
  executablePath = path.join(executablePaths[0], 'Contents', 'MacOS', path.basename(executablePaths[0], '.app'))
} else if (process.platform === 'linux') {
  const executablePaths = glob.sync(path.join(CONFIG.buildOutputPath, '**', 'atom'))
  assert(executablePaths.length === 1, `More than one application to run tests against was found. ${executablePaths.join(',')}`)
  executablePath = executablePaths[0]
} else if (process.platform === 'win32') {
  const executablePaths = glob.sync(path.join(CONFIG.buildOutputPath, '**', 'atom.exe'))
  assert(executablePaths.length === 1, `More than one application to run tests against was found. ${executablePaths.join(',')}`)
  executablePath = executablePaths[0]
} else {
  throw new Error('Running tests on this platform is not supported.')
}

function prepareEnv (suiteName) {
  const env = Object.assign({}, process.env)

  if (process.env.TEST_JUNIT_XML_ROOT) {
    // Tell Jasmine to output this suite's results as a JUnit XML file to a subdirectory of the root, so that a
    // CI system can interpret it.
    const outputPath = path.join(process.env.TEST_JUNIT_XML_ROOT, suiteName, 'test-results.xml')
    env.TEST_JUNIT_XML_PATH = outputPath
  }

  return env
}

function runCoreMainProcessTests (callback) {
  const testPath = path.join(CONFIG.repositoryRootPath, 'spec', 'main-process')
  const testArguments = [
    '--resource-path', resourcePath,
    '--test', '--main-process', testPath
  ]
  const testEnv = Object.assign({}, prepareEnv('core-main-process'), {ATOM_GITHUB_INLINE_GIT_EXEC: 'true'})

  console.log('Executing core main process tests'.bold.green)
  const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit', env: testEnv})
  cp.on('error', error => { callback(error) })
  cp.on('close', exitCode => { callback(null, exitCode) })
}

function runCoreRenderProcessTests (callback) {
  const testPath = path.join(CONFIG.repositoryRootPath, 'spec')
  const testArguments = [
    '--resource-path', resourcePath,
    '--test', testPath
  ]
  const testEnv = prepareEnv('core-render-process')

  console.log('Executing core render process tests'.bold.green)
  const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit', env: testEnv})
  cp.on('error', error => { callback(error) })
  cp.on('close', exitCode => { callback(null, exitCode) })
}

// Build an array of functions, each running tests for a different bundled package
const packageTestSuites = []
for (let packageName in CONFIG.appMetadata.packageDependencies) {
  if (process.env.ATOM_PACKAGES_TO_TEST) {
    const packagesToTest = process.env.ATOM_PACKAGES_TO_TEST.split(',').map(pkg => pkg.trim())
    if (!packagesToTest.includes(packageName)) continue
  }

  const repositoryPackagePath = path.join(CONFIG.repositoryRootPath, 'node_modules', packageName)
  const testSubdir = ['spec', 'test'].find(subdir => fs.existsSync(path.join(repositoryPackagePath, subdir)))

  if (!testSubdir) {
    packageTestSuites.push(function (callback) {
      console.log(`Skipping tests for ${packageName} because no test folder was found`.bold.yellow)
      callback(null, 0)
    })
    continue
  }

  const testFolder = path.join(repositoryPackagePath, testSubdir)

  packageTestSuites.push(function (callback) {
    const testArguments = [
      '--resource-path', resourcePath,
      '--test', testFolder
    ]
    const testEnv = prepareEnv(`bundled-package-${packageName}`)

    const pkgJsonPath = path.join(repositoryPackagePath, 'package.json')
    const nodeModulesPath = path.join(repositoryPackagePath, 'node_modules')
    let finalize = () => null
    if (require(pkgJsonPath).atomTestRunner) {
      console.log(`Installing test runner dependencies for ${packageName}`.bold.green)
      if (fs.existsSync(nodeModulesPath)) {
        const backup = backupNodeModules(repositoryPackagePath)
        finalize = backup.restore
      } else {
        finalize = () => fs.removeSync(nodeModulesPath)
      }
      runApmInstall(repositoryPackagePath)
      console.log(`Executing ${packageName} tests`.green)
    } else {
      console.log(`Executing ${packageName} tests`.bold.green)
    }
    const cp = childProcess.spawn(executablePath, testArguments, {env: testEnv})
    let stderrOutput = ''
    cp.stderr.on('data', data => { stderrOutput += data })
    cp.stdout.on('data', data => { stderrOutput += data })
    cp.on('error', error => {
      finalize()
      callback(error)
    })
    cp.on('close', exitCode => {
      if (exitCode !== 0) {
        console.log(`Package tests failed for ${packageName}:`.red)
        console.log(stderrOutput)
      }
      finalize()
      callback(null, exitCode)
    })
  })
}

function runBenchmarkTests (callback) {
  const benchmarksPath = path.join(CONFIG.repositoryRootPath, 'benchmarks')
  const testArguments = ['--benchmark-test', benchmarksPath]
  const testEnv = prepareEnv('benchmark')

  console.log('Executing benchmark tests'.bold.green)
  const cp = childProcess.spawn(executablePath, testArguments, {stdio: 'inherit', env: testEnv})
  cp.on('error', error => { callback(error) })
  cp.on('close', exitCode => { callback(null, exitCode) })
}

let testSuitesToRun = testSuitesForPlatform(process.platform)

function testSuitesForPlatform (platform) {
  switch (platform) {
    case 'darwin':
      return [runCoreMainProcessTests, runCoreRenderProcessTests, runBenchmarkTests].concat(packageTestSuites)
    case 'win32':
      return (process.arch === 'x64') ? [runCoreMainProcessTests, runCoreRenderProcessTests] : [runCoreMainProcessTests]
    case 'linux':
      return [runCoreMainProcessTests]
    default:
      console.log(`Unrecognized platform: ${platform}`)
      return []
  }
}

async.series(testSuitesToRun, function (err, exitCodes) {
  if (err) {
    console.error(err)
    process.exit(1)
  } else {
    const testsPassed = exitCodes.every(exitCode => exitCode === 0)
    process.exit(testsPassed ? 0 : 1)
  }
})
